iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0
JavaScript

JavaScript為什麼筆記本系列 第 24 篇

Day 24|Scope 到底在限制什麼?為什麼有些變數出了大括號就消失了?

  • 分享至 

  • xImage
  •  

Day 23 寫到 Function declaration、function expression、arrow function。

寫著寫著,我發現一直有一個字在旁邊晃來晃去:

Scope

以前看到 Scope,我大概會翻成:

作用域。

然後就沒了。

但「作用域」三個字本身沒有讓我比較懂。

真正讓我卡住的是這種程式:

if (true) {
  const count = 10
}

console.log(count)

為什麼前面明明有:

const count = 10

下面卻用不到?

變數是消失了嗎?


先給 30 秒版本

我現在會先把 Scope 想成:

一個名稱在哪些程式碼範圍內可以被找到。

例如:

const name = 'Anthony'

重點不只是「建立了一個值」。

還有:

name 這個名稱
在哪裡可以被查到?

所以 Scope 管的不是值有沒有存在,而是:

目前這段程式碼,有沒有資格用這個名字找到那個值?


先看最直覺的例子

function greet() {
  const name = 'Anthony'
  console.log(name)
}

greet()

這裡可以正常印出:

Anthony

但如果我改成:

function greet() {
  const name = 'Anthony'
}

console.log(name)

就會出錯。

為什麼?

因為:

const name = 'Anthony'

建立在 greet 這個 function 的 Scope 裡。

所以 function 外面找不到 name。


原來不是「變數被刪掉」

我以前腦中會偷偷想成:

離開 function
↓
name 被刪除

但這其實把兩件事混在一起了。

Scope 先回答的是:

這裡能不能用 name 這個名字?

而不是:

那個值現在還存在記憶體嗎?

所以今天先不要把 Garbage Collection 也拉進來。

先只處理「名稱可見性」。


Block Scope:大括號真的會切出範圍

看這段:

if (true) {
  const count = 10
  console.log(count)
}

裡面可以用 count。

但外面:

if (true) {
  const count = 10
}

console.log(count)

不行。

因為 let 和 const 有 Block Scope。

像這些大括號:

if (...) {
}

for (...) {
}

{
}

都可能形成 block。

所以:

{
  const count = 10
}

可以先想成:

count 只活在這個名稱範圍裡

那 var 呢?

這時候就會發現 JavaScript 又不太整齊。

if (true) {
  var count = 10
}

console.log(count)

這段反而可能可以拿到 count。

因為 var 不是 Block Scope。

它比較偏向 Function Scope。

這也是為什麼現在很多 JavaScript 程式會優先使用:

const
let

而不是一開始什麼都用 var。

不是因為 var 比較舊所以單純淘汰,而是它的 Scope 行為比較容易讓人踩坑。


Function Scope 又是什麼?

看:

function test() {
  var a = 1
  const b = 2
  let c = 3
}

a、b、c 都不能直接在 function 外面使用。

console.log(a)
console.log(b)
console.log(c)

都會有問題。

所以 Function 本身也會形成自己的 Scope。

目前可以先這樣分:

Function Scope
↓
function 裡的名稱範圍

Block Scope
↓
大括號區塊裡 let / const 的名稱範圍

內層為什麼可以看到外層?

再看:

const user = 'Anthony'

function greet() {
  console.log(user)
}

greet()

greet 裡面沒有宣告 user。

為什麼還是找得到?

因為 JavaScript 找名稱時,會先看目前 Scope。

找不到,就往外層 Scope 找。

概念上像:

greet 的 Scope
↓
找 user
↓
沒有
↓
往外層找
↓
找到 user

這就是 Scope Chain 的基本畫面。


那外層為什麼不能反過來看內層?

function greet() {
  const secret = 'hello'
}

console.log(secret)

不行。

因為查找方向不是:

全世界都翻一遍

而是從目前位置往外找。

外層不會主動鑽進某個 function 裡面找名稱。

這樣也合理,不然每個 function 裡面的暫時變數都可能污染外面的世界。


Shadowing:同名變數到底用哪一個?

看:

const name = 'Outside'

function greet() {
  const name = 'Inside'
  console.log(name)
}

greet()

結果會是:

Inside

因為 JavaScript 找 name 時,先看最近的 Scope。

最近已經找到:

const name = 'Inside'

就不需要再往外找。

這種內層名稱把外層同名名稱遮住的情況,叫:

shadowing

所以 Scope Chain 不只是「往外找」。

更完整一點是:

先找最近的
↓
找到就停
↓
找不到才往外

這跟 React 有什麼關係?

其實前面一直在用。

例如:

function Counter() {
  const [count, setCount] = useState(0)

  function handleClick() {
    console.log(count)
  }

  return <button onClick={handleClick}>+</button>
}

handleClick 裡沒有重新宣告 count。

可是它看得到外層 Counter 裡的 count。

因為:

handleClick Scope
↓
沒有 count
↓
往外找
↓
Counter Scope
↓
找到 count

後面學 Closure 時,這條線會變得更重要。


我現在比較懂「變數屬於哪裡」這句話了

以前會說:

這個變數是區域變數。

但其實我沒有真的想過什麼叫區域。

現在比較能拆成:

宣告發生在哪個 Scope
↓
決定名稱能在哪裡被找到

所以寫:

const total = 100

真正要問的不只是:

total 的值是多少?

還要問:

這個 total 是在哪一層 Scope 宣告的?

最容易搞錯的地方

誤解 1:Scope 是在管值有沒有存在

Scope 主要是在管名稱的可見範圍。

誤解 2:大括號一定形成同樣的 Scope

對 let / const 來說,Block Scope 很重要。

但 var 的行為不同。

誤解 3:裡面看得到外面,外面應該也看得到裡面

不是。

名稱查找會從目前 Scope 往外走,不會反過來鑽進內層。

誤解 4:同名變數一定衝突

不一定。

不同 Scope 可以有同名 binding,內層會優先遮住外層。


如果今天只記得一件事

Scope 決定的是:一個名稱在程式的哪些地方可以被找到。

而 JavaScript 查找名稱時:

目前 Scope
↓
找不到
↓
往外層 Scope
↓
再找

這條 Scope Chain 會一路影響後面的 Closure、Hoisting、this,甚至 React 裡的 callback 行為。


下一個「為什麼」

但現在又有一個更奇怪的事情。

sayHello()

function sayHello() {
  console.log('Hello')
}

Function 明明寫在下面,為什麼上面可以先呼叫?

可是:

sayHello()

const sayHello = () => {
  console.log('Hello')
}

卻不行。

很多教學會直接說:

因為 hoisting。

但 Hoisting 真的是 JavaScript 把程式碼搬到最上面嗎?

下一篇再說。


上一篇
Day 23|function、function expression、arrow function 為什麼三種?
下一篇
Day 25|Hoisting 真的是把程式碼搬到最上面嗎?為什麼 function 可以先用,const 卻不行?
系列文
JavaScript為什麼筆記本 共 28 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言